﻿
Imports System.Net
Imports System.Net.Sockets
Imports System.Threading
Imports osi.root.connector
Imports osi.root.envs
Imports osi.root.formation
Imports osi.root.utils
Imports osi.root.procedure
Imports osi.root.lock
Imports osi.service.secure
Imports osi.service.secure.encrypt
Imports osi.service.zip

Partial Public Class powerpoint
    Public ReadOnly token As String
    Public ReadOnly token_bytes() As Byte
    Public ReadOnly host_or_ip As String
    Public ReadOnly port As UInt16
    Public ReadOnly connecting_timeout_ms As Int64
    Public ReadOnly send_rate_sec As Int32
    Public ReadOnly response_timeout_ms As Int64
    Public ReadOnly receive_rate_sec As Int32
    Public ReadOnly max_connecting As Int32
    Public ReadOnly max_connected As Int32
    Public ReadOnly max_lifetime_ms As Int64
    Public ReadOnly no_delay As Boolean
    Public ReadOnly is_outgoing As Boolean
    Public ReadOnly enable_keepalive As Boolean
    Public ReadOnly first_keepalive_ms As UInt32
    Public ReadOnly keepalive_interval_ms As UInt32
    Public ReadOnly ipv4 As Boolean
    Public ReadOnly zipper As zipper
    Public ReadOnly encryptor As encryptor

    Friend Event closing()
    Friend Event connection_generated(ByVal c As TcpClient)

    Private ReadOnly q As qless2(Of connection)
    Private ReadOnly header_data_increment As Int32
    Private ReadOnly GENERATED_CONNECTION_COUNT As Int64
    Private ReadOnly FREE_CONNECTION_COUNT As Int64
    Private exp As singleentry
    Private connected As Int32

    Private Sub New(ByVal token As String,
                    ByVal host_or_ip As String,
                    ByVal port As UInt32,
                    ByVal ipv4 As Boolean,
                    ByVal connecting_timeout_ms As Int64,
                    ByVal send_rate_sec As Int32,
                    ByVal response_timeout_ms As Int64,
                    ByVal receive_rate_sec As Int32,
                    ByVal max_connecting As Int32,
                    ByVal max_connected As Int32,
                    ByVal no_delay As Boolean,
                    ByVal max_lifetime_ms As Int64,
                    ByVal is_outgoing As Boolean,
                    ByVal enable_keepalive As Boolean,
                    ByVal first_keepalive_ms As UInt32,
                    ByVal keepalive_interval_ms As UInt32,
                    ByVal zipper As zipper,
                    ByVal encryptor As encryptor)
        assert(max_connecting > 0)
        assert(max_connected > 0)
        assert(max_connecting <= max_connected)
        assert(is_outgoing Xor String.IsNullOrEmpty(host_or_ip))
        assert(Not zipper Is Nothing)
        assert(Not encryptor Is Nothing)

        Me.token = token
        Me.token_bytes = str_bytes(token)
        Me.host_or_ip = host_or_ip
        Me.port = port
        Me.ipv4 = ipv4
        Me.connecting_timeout_ms = connecting_timeout_ms
        Me.send_rate_sec = send_rate_sec
        Me.response_timeout_ms = response_timeout_ms
        Me.receive_rate_sec = receive_rate_sec
        Me.max_connecting = max_connecting
        Me.max_connected = max_connected
        Me.no_delay = no_delay
        Me.max_lifetime_ms = max_lifetime_ms
        Me.is_outgoing = is_outgoing
        Me.enable_keepalive = enable_keepalive
        Me.first_keepalive_ms = first_keepalive_ms
        Me.keepalive_interval_ms = keepalive_interval_ms
        Me.zipper = zipper
        Me.encryptor = encryptor

        Me.q = New qless2(Of connection)()
        Me.header_data_increment = If(ipv4, 40, 60)
        Me.connected = 0

        Me.GENERATED_CONNECTION_COUNT = counter.register_average_and_last_average(
                                            strcat(strtoupper(identity()),
                                                   "_GENERATED_CONNECTION_COUNT"),
                                            last_average_length:=256,
                                            sample_rate:=1)
        Me.FREE_CONNECTION_COUNT = counter.register_average_and_last_average(
                                            strcat(strtoupper(identity()),
                                                   "_FREE_CONNECTION_COUNT"),
                                            last_average_length:=256,
                                            sample_rate:=1)
    End Sub

    Public Function send_timeout_ms(ByVal count As Int64) As Int64
        Return rate_to_ms(send_rate_sec, count + header_data_increment)
    End Function

    Public Function receive_timeout_ms(ByVal count As Int64) As Int64
        Return rate_to_ms(receive_rate_sec, count + header_data_increment)
    End Function

    Public Function expired() As Boolean
        Return exp.in_use()
    End Function

    Public Function connected_count() As Int32
        Return connected
    End Function

    Public Function free_count() As Int32
        counter.increase(FREE_CONNECTION_COUNT, q.size())
        Return q.size()
    End Function

    Private Function pop(ByRef r As connection) As Boolean
        While q.pop(r) AndAlso assert(Not r Is Nothing)
            If r.no_refer_client().alive() Then
                Return True
            Else
                assert(Not release(r))
            End If
        End While
        Return False
    End Function

    Private Function push(ByVal c As connection) As Boolean
        Try
            If c Is Nothing OrElse
               Not c.no_refer_client().alive() Then
                Return False
            Else
                q.push(c)
                Return True
            End If
        Catch
            Return False
        End Try
    End Function

    'for closer only
    Friend Function [get](ByRef r As connection) As Boolean
        Return Not expired() AndAlso pop(r)
    End Function

    'for closer only
    Friend Function release(ByVal c As connection) As Boolean
        If push(c) Then
            Return True
        Else
            If tcp_trace Then
                raise_error("connection has been removed from powerpoint ", identity())
            End If
            assert(Interlocked.Decrement(connected) >= 0)
            counter.increase(GENERATED_CONNECTION_COUNT, connected)
            Return False
        End If
    End Function

    Public Function [get](ByRef r As TcpClient) As Boolean
        Dim c As connection = Nothing
        Return [get](c) AndAlso eva(r, c.client())
    End Function

    Public Function release(ByVal client As TcpClient) As Boolean
        If client Is Nothing Then
            Return False
        Else
            Return release(New connection(client))
        End If
    End Function

    'for accepter / connector
    Friend Function insert(ByVal client As TcpClient) As Boolean
        If expired() OrElse client Is Nothing Then
            Return False
        ElseIf Interlocked.Increment(connected) > max_connected OrElse
               Not push(New connection(client)) Then
            assert(Interlocked.Decrement(connected) >= 0)
            If tcp_trace Then
                raise_error("cannot insert new conneciton to the powerpoint ",
                            identity(),
                            " because of the connection count limitation or the connection has been shutdown already")
            End If
            Return False
        Else
            If tcp_trace Then
                raise_error("new conneciton has been generated to the powerpoint ", identity())
            End If
            client.set_no_delay(no_delay)
            client.set_keepalive(enable_keepalive, first_keepalive_ms, keepalive_interval_ms)
            counter.increase(GENERATED_CONNECTION_COUNT, connected)
            RaiseEvent connection_generated(client)
            Return True
        End If
    End Function

    Public Sub close()
        If exp.mark_in_use() Then
            RaiseEvent closing()
            Dim c As connection = Nothing
            While connected > 0
                If pop(c) Then
                    release(c)
                Else
                    timeslice_sleep_wait_when(Function() q.empty() AndAlso connected > 0)
                End If
            End While
            assert(q.empty())
        End If
        GC.KeepAlive(q)
        GC.KeepAlive(exp)
    End Sub

    Public Function zip_enabled() As Boolean
        Return Not zipper.bypass()
    End Function

    Public Function zip_enabled(ByVal i() As Byte) As Boolean
        Return Not isemptyarray(i) AndAlso
               zip_enabled()
    End Function

    Public Function encrypt_enabled() As Boolean
        Return Not encryptor.bypass() AndAlso
               Not isemptyarray(token_bytes)
    End Function

    Public Function encrypt_enabled(ByVal i() As Byte) As Boolean
        Return Not isemptyarray(i) AndAlso
               encrypt_enabled()
    End Function

    Public Function zip_and_encrypt(ByVal i() As Byte) As Byte()
        If encrypt_enabled(i) Then
            i = encryptor.encrypt(token_bytes, i)
        End If
        If zip_enabled(i) Then
            i = zipper.zip(i)
        End If
        Return i
    End Function

    Public Function unzip_and_decrypt(ByVal i() As Byte) As Byte()
        If zip_enabled(i) Then
            i = zipper.unzip(i)
        End If
        If encrypt_enabled(i) Then
            i = encryptor.decrypt(token_bytes, i)
        End If
        Return i
    End Function

    Protected Overrides Sub Finalize()
        close()
        MyBase.Finalize()
    End Sub

    Public Function identity() As String
        If is_outgoing Then
            Return strcat("outgoing@", host_or_ip, ":", port)
        Else
            Return strcat("incoming@", port)
        End If
    End Function

    'test purpose only, make sure all the related event_combs and stopwatch events are stopped
    Public Shared Sub waitfor_stop(ByVal ParamArray this() As powerpoint)
        sleep(stop_milliseconds(this))
    End Sub

    'the longest time in milliseconds to let all the related event_combs stop
    Public Shared Function stop_milliseconds(ByVal ParamArray this() As powerpoint) As Int64
        Dim m As Int64 = 0
        m = max(constants.interval_ms.closer_all_poll_alive,
                constants.interval_ms.watch_connectivity)
        m = max(m, constants.interval_ms.watch_expiration)
        m = max(m, constants.interval_ms.connector_check)
        m = max(m, constants.interval_ms.connector_fail)
        For i As Int32 = 0 To array_size(this) - 1
            If Not this(i) Is Nothing AndAlso
               this(i).is_outgoing Then
                m = max(m, this(i).connecting_timeout_ms)
            End If
        Next
        'accepter should be finished almost immediately
        Return m
    End Function
End Class
